AmplifyとVueを使ってS3をラップしたアプリケーションを作ってみた
Aさん「S3でファイル共有したいね」 Bさん「いいねー、じゃあIAMユーザーを、、」 Aさん「おっといけない、うちのルールではIAMユーザーを払い出すことはできないんだ」 Bさん「じゃあ、S3のバケットポリシーでIPアドレスせいげ、、」 Aさん「おっといけない、認証機能は必須なんだ」 Bさん「じゃあ、Amplifyで簡単なアプリケーションを作るか」
というのが本ブログです。こんな感じのアプリを作成します。
それではやってみましょう!!
プロジェクト作成
AWS Amplify Vue Starterをベースにアプリケーションを作成します。
対象のプロジェクトを取得し必要なライブラリをインストールします。
$ git clone https://github.com/aws-samples/aws-amplify-vue.git $ cd aws-amplify-vue $ yarn install
UIのコンポーネントも合わせてインストールします。
$ yarn add element-ui
Amplify Frameworkをインストール
Amplify Frameworkをインストールします。
$ npm install -g @aws-amplify/cli $ amplify version 3.0.0
AmplifyでAWSリソースを作成
Amplifyをセットアップします。対話形式で設定を行います。
$ amplify init Note: It is recommended to run this command from the root of your app directory ? Enter a name for the project s3-app ? Enter a name for the environment dev ? Choose your default editor: Visual Studio Code ? Choose the type of app that you're building javascript Please tell us about your project ? What javascript framework are you using vue ? Source Directory Path: src ? Distribution Directory Path: dist ? Build Command: npm run-script build ? Start Command: npm run-script serve Using default provider awscloudformation For more information on AWS Profiles, see: https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html ? Do you want to use an AWS profile? Yes ? Please choose the profile you want to use hoge ? Enter the MFA token code: ⠏ Initializing project in the cloud...
認証(Cognito)機能を追加します。
$ amplify add auth Using service: Cognito, provided by: awscloudformation The current configured provider is Amazon Cognito. Do you want to use the default authentication and security configuration? Default configuration Warning: you will not be able to edit these selections. How do you want users to be able to sign in? Username Do you want to configure advanced settings? No, I am done. Successfully added resource s3app0a645faf locally Some next steps: "amplify push" will build all your local backend resources and provision it in the cloud "amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud
ストレージ(S3)機能を追加します。S3を操作できるユーザーは認証済ユーザーのみとします。
$ amplify add storage ? Please select from one of the below mentioned services Content (Images, audio, video, etc.) ? Please provide a friendly name for your resource that will be used to label this category in the project: s329f8afc1 ? Please provide bucket name: s3-appd2834b29a3a946e0a85699ad2dc7633e ? Who should have access: Auth users only ? What kind of access do you want for Authenticated users? create/update, read, delete ? Do you want to add a Lambda Trigger for your S3 Bucket? No Successfully added resource s329f8afc1 locally Some next steps: "amplify push" builds all of your local backend resources and provisions them in the cloud "amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
リソースをデプロイします。これによりCognito、S3などのリソースがAWS上に作成されます。
$ amplify push Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | ------------- | --------- | ----------------- | | Auth | XXXXXXXXXXXXX | Create | awscloudformation | | Storage | XXXXXXXXXX | Create | awscloudformation | ? Are you sure you want to continue? Yes ✔ All resources are updated in the cloud
フロント部分を修正
src/Home.vue
を以下のように変更します。Upload
やDownload
、Delete
に対応したファンクションをそれぞれ作成します。
<template> <div class="container shifted"> <h1 class="h1"> S3 Objects </h1> <el-button> <label for="file"> Upload <input type="file" @change="upload" id="file" style="display:none;"> </label> </el-button> <el-button @click="refresh" class="el-icon-refresh-left"></el-button> <el-table :data="s3Data" style="width: 100%"> <el-table-column prop="key" label="Key" sortable> </el-table-column> <el-table-column prop='lastModified' label="LastModified" sortable> </el-table-column> <el-table-column prop="size" label="Size" sortable> </el-table-column> <el-table-column> <template slot-scope="scope"> <el-button @click="download(scope.row)">Download</el-button> <el-button type="danger" @click="openDeleteDialog(scope.row)">Delete</el-button> </template> </el-table-column> </el-table> <el-dialog title="Delete objects" :visible.sync="dialogVisible" width="30%" :before-close="handleClose"> <span>{{ deleteObject }} Objects will be deleted</span> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">Cancel</el-button> <el-button type="primary" @click="deleteOK()">Confirm</el-button> </span> </el-dialog> </div> </template> <script> import Vue from 'vue' import Amplify, { API,Storage } from 'aws-amplify'; import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import locale from 'element-ui/lib/locale/lang/en' Vue.use(ElementUI, { locale }) export default { name: 'Home', data () { return { s3Data: [], dialogVisible: false, selectedRow: "", deleteObject: "", uploadFile: "" } }, created () { this.refresh() }, methods: { refresh () { Storage.list('') .then(result => this.s3Data = JSON.parse(JSON.stringify(result))) .catch(err => console.log(err)); }, upload(e) { var files = e.target.files || e.dataTransfer.files; console.log(files) Storage.put(files[0].name, files[0]) .then(result => { console.log(result) this.refresh() }) .catch(err => console.log(err)); }, download (row) { Storage.get(row['key'], { download: true }) .then(result => { console.log(result) const url = URL.createObjectURL(new Blob([result.Body])); const link = document.createElement('a') link.href = url link.download = row['key'] console.log(link) link.click() }) .catch(err => console.log(err)); }, openDeleteDialog(row) { this.selectedRow = row; console.log(row) this.deleteObject = row['key']; this.dialogVisible = true }, deleteOK () { this.dialogVisible = false Storage.remove(this.selectedRow['key']) .then(result => { console.log(result) this.refresh() } ) .catch(err => console.log(err)); }, handleClose(done) { this.$confirm('Are you sure to close this dialog?') .then(_ => { done(); }) .catch(_ => {}); } } } </script> <style> label { color: #606266; background-color:white; padding: 10px; } </style>
ローカルでの動作確認
ローカルで動作を確認してみます。
$ yarn run dev Your application is running here: http://localhost:8080
上記のURLに接続するとログイン画面が表示されます。Create account
よりユーザーを作成しログインしてみましょう。
するとこのような画面が表示されます。
アップロード、ダウンロード、削除も問題なく実行できます。
ファイルの実態はamplify push
にて作成したS3に保存されています。
S3でホスティング
ローカルで動作確認ができたのでS3にアップロードしてインターネットから接続できる状態にしてみましょう。
$ amplify hosting add ? Select the environment setup: DEV (S3 only with HTTP) ? hosting bucket name aws-amplify-vue-20190905141756-hostingbucket ? index doc for the website index.html ? error doc for the website index.html You can now publish your app using the following command: Command: amplify publish
アプリケーションをビルドし静的ファイルを出力します。
$ yarn build $ ll ./dist/ total 8 -rw-r--r-- 1 jogan.naoki staff 524B 9 5 14:22 index.html drwxr-xr-x 6 jogan.naoki staff 192B 9 5 14:22 static
S3にアップロードし画面を確認してみます。
$ amplify publish frontend build command exited with code 0 ✔ Uploaded files successfully. Your app is published successfully. http://XXXXXXXXXXXXX.s3-website-ap-northeast-1.amazonaws.com
問題なさそうです。
また、不特定のユーザのアクセスを禁止する場合は、Cognitoの管理画面よりサインアップの設定を変更しておきましょう。
IP制限を追加したい場合はバケットポリシーで制限すれば良さそうですね。
さいごに
Amplify+Vueを使ってS3を操作できる簡単なアプリケーションを作成してみました。ユースケースはあまりないかもしれませんがハマる場合は使ってみてください。(アクセスログを取得する場合はAPIGateway経由でS3のPre-signedURL発行する必要がありそうです)